<?php
/**
 *  全局编码类方法
 * 
 * Copyright (c) 2014~2019 https://desion.net All rights reserved.
 * Author: 阿林 QQ:120867629 <linhong00@gmail.com>
 */
declare (strict_types = 1);
namespace dsphp\helper;

class CodeHelper
{
    // 编码长度
    const CKEY_LEN = 4;
    // 最大有效期 (30天)
    const MAX_EXPIRY = 2592000;
    // 密钥
    private $_key = '';

    /**
     * 设置密钥
     *
     * @param string $key 密钥
     * @return this
     */
    public function key(string $key)
    {
        $this->_key = $key;
        return $this;
    }

    /**
     * 读取密钥
     */
    public function getKey():string
    {
        return $this->_key;
    }

    /**
     * 加密字符串
     *
     * @param mixed   $data      加密数据
     * @param integer $expiry  有效期，0 为长期， 最长为30天
     */
    public function encode($data, int $expiry = 0)
    {
        if (is_numeric($data)) {
            $data = (string)$data;
        }

        if (! is_string($data) && ! is_array($data)) {
            return '';
        }

        $data   = json_encode($data);
        $expiry = $this->_checkExpiry($expiry);

        return $this->_doEnCode($data, $expiry);
    }

    /**
     * 还原字符串
     *
     * @param string  $data
     * @param boolean $status
     * @return mixed
     */
    public function decode(string $data, bool $status = false)
    {
        list($result, $keyb) = $this->_deKey($data);

        $time = intval(substr($result, 0, 10));
        $diff = $time - time();

        if (($time == 0 || $diff > 0) && substr($result, 10, 16) == substr(md5(substr
            ($result, 26) . $keyb), 0, 16)) {
            $result = substr($result, 26);
        } else {
            return '';
        }

        $code = json_decode($result, true);

        if ($status) {
            $result = ['code' => $code, 'data' => $result, 'expiry' => $diff];
        } 
        
        return $code;
    }

    /**
     * 检测有效时间
     *
     * @param integer $expiry  有效时间（秒）
     * @return integer
     */
    private function _checkExpiry(int $expiry)
    {
        if ($expiry < 0 || $expiry > self::MAX_EXPIRY) {
            $expiry = self::MAX_EXPIRY;
        }

        return $expiry;
    }

    /**
     * 编码key
     *
     * @param string $data     加密数据
     * @param integer $expiry  有效时间
     */
    private function _doEnCode(string $data, int $expiry):string
    {
        // 有效期
        $expiry = $expiry ? $expiry + time() : 0;
        // key
        $keys = $this->_key();
        $keyc = substr(md5(microtime()), -(self::CKEY_LEN));
        $data = sprintf('%010d', $expiry) . substr(md5($data . $keys['b']), 0, 16) . $data;

        $data = $this->_make([$keys['a'], $keys['b'], $keyc, $data]);

        $data = str_replace('=', '', base64_encode($data));

        $data = str_replace('+', '_', $data);
        $data = str_replace('/', '+', $data);

       return $keyc . $data;
    }

    /**
     * 解码KEY
     * 
     * @param string $data  解密字符串
     */
    private function _deKey(string $data):array
    {
        $data = str_replace('+', '/', $data);
        $data = str_replace('_', '+', $data);

        $keys = $this->_key();
        $keyc = substr($data, 0, self::CKEY_LEN);
        $data = base64_decode(substr($data, self::CKEY_LEN));

        $data = $this->_make([$keys['a'], $keys['b'], $keyc, $data]);

        return [$data, $keys['b']];
    }

    /**
     * 根据密钥生成编码key
     */
    private function _key():array
    {
        $key  = md5($this->_key);
        $keya = md5(substr($key, 0, 16));
        $keyb = md5(substr($key, 16, 16));

        return ['a' => $keya, 'b' => $keyb];
    }

    /**
     * 执行编码
     *
     * @param array $data
     * @return string
     */
    private function _make(array $data)
    {
        list($keya, $keyb, $keyc, $string) = $data;

        $cryptKey     = $keya . md5($keya . $keyc);
        $stringLength = strlen($string);
        
        $result = '';
        $box    = range(0, 255);

        $rndKey = [];
        for ($i = 0; $i <= 255; $i++) {
            $rndKey[$i] = ord($cryptKey[$i % 64]);
        }

        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndKey[$i]) % 256;
            list($box[$i], $box[$j]) = [$box[$j], $box[$i]];
        }

        for ($a = $j = $i = 0; $i < $stringLength; $i++) {
            $a   = ($a + 1) % 256;
            $j   = ($j + $box[$a]) % 256;

            list($box[$a], $box[$j]) = [$box[$j], $box[$a]];

            $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }

        return $result;
    }
}